// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package net

import (
	"context"
	"internal/itoa"
	"syscall"
)

// BUG（mikio）：在计划9中，UDPConn的ReadMsgUDP和
// WriteMsgUDP方法未实现。

// 错误（mikio）：在Windows上，UDPConn的文件方法未实现
// 错误。

// BUG（mikio）：在JS上，与UDPConn相关的方法和函数没有实现。

// UDPAddr表示UDP端点的地址。
type UDPAddr struct {
	IP   IP
	Port int
	Zone string // IPv6作用域寻址区域
}

// 网络返回地址的网络名称“udp”。
func (a *UDPAddr) Network() string { return "udp" }

func (a *UDPAddr) String() string {
	if a == nil {
		return "<nil>"
	}
	ip := ipEmptyString(a.IP)
	if a.Zone != "" {
		return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port))
	}
	return JoinHostPort(ip, itoa.Itoa(a.Port))
}

func (a *UDPAddr) isWildcard() bool {
	if a == nil || a.IP == nil {
		return true
	}
	return a.IP.IsUnspecified()
}

func (a *UDPAddr) opAddr() Addr {
	if a == nil {
		return nil
	}
	return a
}

// ResolveUDPAddr返回UDP端点的地址。
// 
// 网络必须是UDP网络名称。
// 
// 如果地址参数中的主机不是文字IP地址或
// 端口不是文字端口号，则ResolveUDPAddr将
// 地址解析为UDP端点的地址。
// 否则，它将地址解析为一对文字IP地址
// 和端口号。
// address参数可以使用主机名，但不推荐使用
// ，因为它最多会返回主机名的一个
// IP地址。
// 
// 有关网络和地址的说明，请参阅func Dial 
// 参数。
func ResolveUDPAddr(network, address string) (*UDPAddr, error) {
	switch network {
	case "udp", "udp4", "udp6":
	case "": // Go 1.0未记录行为的提示通配符
		network = "udp"
	default:
		return nil, UnknownNetworkError(network)
	}
	addrs, err := DefaultResolver.internetAddrList(context.Background(), network, address)
	if err != nil {
		return nil, err
	}
	return addrs.forResolve(network, address).(*UDPAddr), nil
}

// UDPConn是用于UDP网络连接的Conn和PacketConn接口
// 的实现。
type UDPConn struct {
	conn
}

// SyscallConn返回原始网络连接。
// 这实现了syscall.Conn接口。
func (c *UDPConn) SyscallConn() (syscall.RawConn, error) {
	if !c.ok() {
		return nil, syscall.EINVAL
	}
	return newRawConn(c.fd)
}

// ReadFromUDP的行为类似于ReadFrom，但返回一个UDPADD。
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) {
	// 此函数旨在允许调用方控制返回的*UDPAddr的生存期
	// 从而防止分配。
	// 参见https:
	// 真正的工作是通过下面的readFromUDP完成的。
	return c.readFromUDP(b, &UDPAddr{})
}

// readFromUDP实现readFromUDP。
func (c *UDPConn) readFromUDP(b []byte, addr *UDPAddr) (int, *UDPAddr, error) {
	if !c.ok() {
		return 0, nil, syscall.EINVAL
	}
	n, addr, err := c.readFrom(b, addr)
	if err != nil {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	return n, addr, err
}

// ReadFrom实现PacketConn ReadFrom方法。
func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) {
	n, addr, err := c.readFromUDP(b, &UDPAddr{})
	if addr == nil {
		// 返回地址（nil），而不是地址（*UDPConn（nil））。
		return n, nil, err
	}
	return n, addr, err
}

// ReadMsgUDP从c读取消息，将有效负载复制到b中，并将相关带外数据复制到oob中。它返回复制到b的
// 字节数、复制到oob的字节数、在消息上设置的标志
// 以及消息的源地址。
// 
// golang.org/x/net/ipv4和golang.org/x/net/ipv6包可以是
// 用于操作oob中的IP级别套接字选项。
func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error) {
	if !c.ok() {
		return 0, 0, 0, nil, syscall.EINVAL
	}
	n, oobn, flags, addr, err = c.readMsg(b, oob)
	if err != nil {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	return
}

// WriteToUDP的行为类似于WriteTo，但使用UDPADD。
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	n, err := c.writeTo(b, addr)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err}
	}
	return n, err
}

// WriteTo实现PacketConn WriteTo方法。
func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	a, ok := addr.(*UDPAddr)
	if !ok {
		return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr, Err: syscall.EINVAL}
	}
	n, err := c.writeTo(b, a)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: a.opAddr(), Err: err}
	}
	return n, err
}

// WriteMsgUDP在c未连接的情况下通过c将消息写入addr，或者在c已连接的情况下将消息写入
// c的远程地址（在这种情况下，addr必须是
// nil）。有效负载从b复制，相关带外
// 数据从oob复制。它返回有效负载和
// 写入的带外字节数。
// 
// golang.org/x/net/ipv4和golang.org/x/net/ipv6包可以是
// 用于操作oob中的IP级别套接字选项。
func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err error) {
	if !c.ok() {
		return 0, 0, syscall.EINVAL
	}
	n, oobn, err = c.writeMsg(b, oob, addr)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err}
	}
	return
}

func newUDPConn(fd *netFD) *UDPConn { return &UDPConn{conn{fd}} }

// DialUDP的作用类似于UDP网络的拨号。
// 
// 网络必须是UDP网络名；有关详细信息，请参阅func拨号。
// 
// 如果laddr为零，则自动选择本地地址。
// 如果raddr的IP字段为零或未指定的IP地址，则假定为
// 本地系统。
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) {
	switch network {
	case "udp", "udp4", "udp6":
	default:
		return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(network)}
	}
	if raddr == nil {
		return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress}
	}
	sd := &sysDialer{network: network, address: raddr.String()}
	c, err := sd.dialUDP(context.Background(), laddr, raddr)
	if err != nil {
		return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err}
	}
	return c, nil
}

// ListenUDP的作用类似于UDP网络的ListenPacket。
// 
// 网络必须是UDP网络名；有关详细信息，请参阅func拨号。
// 
// 如果laddr的IP字段为零或未指定的IP地址，
// ListenUDP将侦听本地系统除多播IP地址之外的所有可用IP地址。
// 如果laddr的端口字段为0，则会自动选择端口号
// 。
func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error) {
	switch network {
	case "udp", "udp4", "udp6":
	default:
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(network)}
	}
	if laddr == nil {
		laddr = &UDPAddr{}
	}
	sl := &sysListener{network: network, address: laddr.String()}
	c, err := sl.listenUDP(context.Background(), laddr)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: laddr.opAddr(), Err: err}
	}
	return c, nil
}

// ListenMulticastUDP与UDP网络的ListenPackage类似，但
// 在特定网络接口上获取组地址。
// 
// 网络必须是UDP网络名；有关详细信息，请参阅func拨号。
// 
// ListenMulticastUDP侦听
// 本地系统，包括组、多播IP地址。
// 如果ifi为零，ListenMulticastUDP使用系统分配的
// 多播接口，但不建议这样做，因为
// 分配取决于平台，有时可能需要
// 路由配置。
// 如果gaddr的端口字段为0，则自动选择端口号
// 。
// 
// ListenMulticastUDP只是为了方便简单、小型的
// 应用程序。有golang.org/x/net/ipv4和
// golang.org/x/net/ipv6通用包。
// 
// 注意，ListenMulticastUDP将IPPROTO_IP下的IP_MULTICAST_循环套接字选项
// 设置为0，以禁用多播数据包的环回。
func ListenMulticastUDP(network string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error) {
	switch network {
	case "udp", "udp4", "udp6":
	default:
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: UnknownNetworkError(network)}
	}
	if gaddr == nil || gaddr.IP == nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: errMissingAddress}
	}
	sl := &sysListener{network: network, address: gaddr.String()}
	c, err := sl.listenMulticastUDP(context.Background(), ifi, gaddr)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: err}
	}
	return c, nil
}
